﻿using Common.AspNetCore.Structs;
using Common.AspNetCore.Utils;
using Common.Cache;
using Common.EF;
using Common.EF.Utils;
using Common.Infrastructure;
using Common.Infrastructure.Logs;
using Common.Infrastructure.Service;
using Common.Infrastructure.Utils;
using Common.MQ;
using DotNetCore.CAP.Messages;
using DotNetCore.CAP.Transport;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens;
using NConsul.AspNetCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Common.AspNetCore
{
    public class CommonStartupBase
    {
        private IConfiguration configuration;

        public CommonStartupBase(IConfiguration configuration)
        {
            this.configuration = configuration;

            ////net6.0 针对pg 添加时区配置
            AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
            AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);

            var consulAddress = this.configuration["Consul:Address"];
            if (!string.IsNullOrEmpty(consulAddress))
            {
                AddressService.InitServiceCode(this.configuration["ApiKey"], this.configuration["Consul:InitServiceCode"]);
#if DEBUG
                consulAddress = "http://localhost:8500/";
#endif
                AddressService.Start(consulAddress, this.configuration["Consul:Http"]);
            }
        }

        public IConfiguration Config
        {
            get { return this.configuration; }
        }

        public virtual void RegisterRedisCache(IServiceCollection services)
        { 
            services.AddSingleton<IRedisCacheService, RedisCacheService>();
        }

        public virtual void RegisterBaseComponents(IServiceCollection services)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddSingleton<IComonCacheService, CommonCacheService>();
            services.AddScoped<ILogService, LogService>();
            services.AddScoped<IAddressService, AddressService>();
            services.AddScoped<IServiceCallService, ServiceCallService>();
            services.AddScoped<IMessageService, MessageService>();
            services.AddScoped<IUserTokenService, UserTokenService>();
            services.AddHttpClient();
            MessageStorageUtils.InitMessage(); 
        }

        public virtual void RegisterCap(IServiceCollection services)
        {
            var connMq = this.configuration["MQ:Host"];
            if (string.IsNullOrEmpty(connMq))
            {
                return;
            }

            var user = this.configuration["MQ:User"];
            var password = this.configuration["MQ:Password"];
            var defaultGroup = this.configuration["MQ:DefaultGroup"];
            var defaultExchangeName = this.configuration["MQ:DefaultExchangeName"];
            var failedRetryCount = this.configuration["MQ:FailedRetryCount"];
            var producerThreadCount = this.configuration["MQ:ProducerThreadCount"];
            var consumerThreadCount = this.configuration["MQ:ConsumerThreadCount"];
            var useNoStore = this.configuration["MQ:UseNoStore"]; 
            var connMqPg = this.configuration["MQ:PGConn"];

            var p = new CommonContextPoolParam(null, this.configuration);
            var defaultContext = p.Contexts.FirstOrDefault(t => t.ContextType == ContextTypeEnum.DefaultContext);

            services.TryAddSingleton<IDispatcher, CapCustomDispatcher>();  
            services.AddCap(x =>
            {
                if (LibSysUtils.ToBoolean(useNoStore))
                {
                    x.UseNoStorage();
                }
                else if (!string.IsNullOrEmpty(connMqPg))
                {
                    x.UsePostgreSql(connMqPg);
                }
                else if (defaultContext != null)
                {
                    x.UsePostgreSql(defaultContext.ConnString);
                }

                x.UseRabbitMQ((x1) =>
                {
                    x1.HostName = connMq;
                    if (!string.IsNullOrEmpty(password))
                    {
                        x1.Password = password;
                    }

                    if (!string.IsNullOrEmpty(user))
                    {
                        x1.UserName = user;
                    }

                    if (!string.IsNullOrEmpty(defaultExchangeName))
                    {
                        x1.ExchangeName = defaultExchangeName;
                    }
                });
                x.FailedRetryCount = string.IsNullOrEmpty(failedRetryCount) ? 3 : LibSysUtils.ToInt(failedRetryCount);
                x.ProducerThreadCount = string.IsNullOrEmpty(producerThreadCount) ? 1 : LibSysUtils.ToInt(producerThreadCount);
                x.ConsumerThreadCount = string.IsNullOrEmpty(consumerThreadCount) ? 1 : LibSysUtils.ToInt(consumerThreadCount);
                if (!string.IsNullOrEmpty(defaultGroup))
                {
                    x.DefaultGroupName = defaultGroup;
                }

                x.FailedThresholdCallback = msg =>
                {
                    try
                    {
                        string error = null;
                        msg.Message?.Headers.TryGetValue(Headers.Exception, out error);

                        var comon = msg.ServiceProvider.GetService<IComonCacheService>();
                        var st = new CapErrorLogModel()
                        {
                            ServiceCode = comon.GetServiceCode(),
                            ApiKey = comon.GetApiKey(),
                            CapId = msg.Message?.GetId(),
                            CapType = (int)msg.MessageType,
                            GroupName = msg.Message?.GetGroup(),
                            Message = error,
                            Name = msg.Message?.GetName(),
                        };

                        _ = WebApiUtils.PostByResponse($"{comon.GetGroupUrl()}/api/Log/CapErrorLog/AddCapErrorLog", st);
                    }
                    catch
                    {
                    }
                };
            });
        }

        public virtual void RegisterContextServices(IServiceCollection services)
        {
            services.AddScoped<ICommonContext, CommonContext>();
            services.AddScoped<ICommonContextPool, CommonContextPool>();
            services.AddScoped<ICommonRepository, CommonRepository>();
            services.AddScoped<ICommonContextPoolParam, CommonContextPoolParam>();
            EntityInitUtils.Init(this.configuration); 
        }

        public virtual void RegisterServices(IServiceCollection services)
        { 
            this.RegisterBaseComponents(services);
            this.RegisterRedisCache(services);
            this.RegisterCap(services);
            this.RegisterConsulConfig(services);
            this.RegisterContextServices(services);
            this.RegisterModel(services);
            this.RegisterService(services);
            this.RegisterBearAuth(services);

            services.AddCors(
                o =>
                 o.AddPolicy(
                     "AllowAllPolicy",
                     builder =>
                     {
                         builder.AllowAnyOrigin()
                                .AllowAnyMethod()
                                .AllowAnyHeader();
                     }));
        }

        public void RegisterBearAuth(IServiceCollection services)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                  .AddJwtBearer(options =>
                  {
                      options.TokenValidationParameters = new TokenValidationParameters
                      {
                          ValidateIssuer = true, ////是否验证Issuer
                          ValidateAudience = true, ////是否验证Audience
                          ValidateLifetime = true, ////是否验证失效时间
                          ClockSkew = TimeSpan.FromSeconds(600),
                          ValidateIssuerSigningKey = true, ////是否验证SecurityKey
                          ValidAudience = "leedarson.com", ////Audience
                          ValidIssuer = "leedarson.com", ////Issuer，这两项和前面签发jwt的设置一致
                          IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MGRmYjc3ODg4ZDQ2NDE0ZTkxZWNiZjcxNjY1NTIwMWQ=")) ////拿到SecurityKey
                      };
                  });
        }

        public virtual void RegisterService(IServiceCollection services)
        {
            var entryAssembly = Assembly.GetEntryAssembly()?.GetName().Name;
            var toReisterServices = Assembly.Load(entryAssembly).GetTypes().Where(w => w.IsSubclassOf(typeof(CommonService)) && w.Name.EndsWith("Service"));
            foreach (var item in toReisterServices)
            {
                services.AddScoped(item);
            }

            var toReisterRepositorys = Assembly.Load(entryAssembly).GetTypes().Where(w => w.IsSubclassOf(typeof(CoreRepository)) && w.Name.EndsWith("Repository"));
            foreach (var item in toReisterRepositorys)
            {
                services.AddScoped(item);
            }
        }

        public virtual void RegisterModel(IServiceCollection services)
        {
            services.AddScoped<ICommonModelRepository, CommonModelRepository>();
            AspNetCoreInitUtils.InitModel();
        }

        public virtual void DatabaseMigrate(IServiceProvider provider)
        {
            var p = new CommonContextPoolParam(provider, this.configuration);
            if (!p.Contexts.Any(t => t.ContextType == ContextTypeEnum.DefaultContext))
            {
                return;
            }

            using (var x = new CommonContextPool(provider, p))
            {
                var t = x.DatabaseMigrateAsync(DateTime.Now.AddMonths(-3), null);
                t.Wait();
            }
        }

        public virtual void RegisterConsulConfig(IServiceCollection services)
        {
            var config = GetConsulConfig();
            if (config != null)
            {
                services.AddConsul(config.Address)
                    .AddHttpHealthCheck($"{config.Protocol}://{config.Ip}:{config.Port }/{config.CheckUrl}", config.Timeout, config.Interval)
                      .RegisterService(config.Name, config.Ip, config.Port, config.Tags);
            }
        }

        public virtual void RegisterConfigure(IApplicationBuilder app)
        {
            app.UseCors("AllowAllPolicy");
            var provider = app.ApplicationServices.CreateScope().ServiceProvider;
            DatabaseMigrate(provider);
            DoInitCommonCache(provider);
        }

        protected void DoInitCommonCache(IServiceProvider provider)
        {
            var cache = provider.GetService<IComonCacheService>();
            cache.GetCommonCache();
        }

        protected ConsulConfig GetConsulConfig()
        {
            if (string.IsNullOrEmpty(configuration["Consul:Address"]))
            {
                return null;
            }

            List<string> tags = new List<string>();
            string tag = configuration["Consul:Tag"];
            if (!string.IsNullOrEmpty(tag))
            {
                tags.Add(tag);
            }

            tags.Add("EFApi");
            ConsulConfig config = new ConsulConfig()
            {
                Address = configuration["Consul:Address"],
                Ip = configuration["Consul:Ip"],
                Name = configuration["ServiceCode"],
                Port = LibSysUtils.ToInt(configuration["Consul:Port"]),
                Protocol = configuration["Consul:Http"],
                CheckUrl = configuration["Consul:CheckUrl"],
                Interval = LibSysUtils.ToInt(configuration["Consul:Interval"]),
                Timeout = LibSysUtils.ToInt(configuration["Consul:Timeout"]),
                DeregisterCriticalServiceAfter = LibSysUtils.ToInt(configuration["Consul:DeregisterCriticalServiceAfter"]),
            };
            if (string.IsNullOrEmpty(config.Protocol))
            {
                config.Protocol = "http";
            }

            if (string.Compare(config.Protocol, "https", true) == 0)
            {
                config.IsUseTls = true;
            }

            if (config.Interval == 0)
            {
                config.Interval = 10;
            }

            if (config.Timeout == 0)
            {
                config.Timeout = 10;
            }

            if (string.IsNullOrEmpty(config.CheckUrl))
            {
                config.CheckUrl = "Index";
            }

            if (config.DeregisterCriticalServiceAfter == 0)
            {
                config.DeregisterCriticalServiceAfter = 5;
            }

            if (config.Port == 0)
            {
                config.Port = LibSysUtils.ToInt(configuration["Consul:LPort"]);
            }

            #if DEBUG
            config.Address = "http://localhost:8500/";
            config.Ip = "localhost";
            #endif

            config.Tags = tags.ToArray();
            return config;
        }
    }
}
